﻿#region using

using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

#endregion

namespace JupiterLibrary
{
    /// <summary>
    /// Modos posibles para calcular el clip
    /// </summary>
    public enum TextureClipModes
    {
        /// <summary>
        /// El pixel 0,0 indica el color del fondo. Este es el modo normal
        /// </summary>
        Pixel0IsBackground,
        /// <summary>
        /// El pixel 0,0 indica el color del rectángulo delimitador de los clips. Se usa en el editor
        /// </summary>
        Pixel0IsRectangleLimiter,
        /// <summary>
        /// El tamaño de los clips está predefinido
        /// </summary>
        FizedSize
    }

    /// <summary>
    /// Gestiona una textura 2D. Puede utilizar el Content para cargarla o bien puede cargarla directamente del stream
    /// </summary>
    public class Texture_2D
    {
        public string FullPath { get; private set; }

        public Texture2D Texture { get; private set; }

        /// <summary>
        /// Obtiene la lista de rectángulos
        /// </summary>
        public List<Rectangle> SpriteRectangles { get; private set; }

        public List<Clip> Clips;

        /// <summary>
        /// Variable para leer los colores
        /// </summary>
        Color[] data;

        /// <summary>
        /// Nombre
        /// </summary>
        public string Name;

        /// <summary>
        /// We do not want that the user can create instances of Texture_2D. He must use Textures.CreateAndAdd
        /// </summary>
        /// <param name="fullpath"></param>
        /// <param name="useContent"></param>
        internal Texture_2D(string fullpath,bool useContent)
        {
            bool methodLog = Jupiter.Log.BeginMethod();

            Jupiter.Log.WriteLine("Trying to Load Texture:" + fullpath);

            FullPath = fullpath;

            if (useContent)
            {
                Texture = Jupiter.Content.Load<Texture2D>(fullpath);
            }
            else
            {
                using (FileStream stream = File.OpenRead(FullPath))
                {
                    Texture = Texture2D.FromStream(Jupiter.Video.GraphicsDevice, stream);
                }
            }

            if (Texture == null)
            {
                Jupiter.Log.WriteLine("FAILED loading Texture:" + fullpath,LogTypes.Error);

            }

            // Write down some information about the texture just loaded

            Jupiter.Log.WriteLine("Texture:" + fullpath+ "LOADED.");
            Jupiter.Log.InsertIndent();
            Jupiter.Log.WriteLine("Bound:" + Texture.Bounds.ToString());
            Jupiter.Log.WriteLine("SurfaceFormat:" + Texture.Format.ToString());
            Jupiter.Log.RemoveIndent();

            SpriteRectangles = new List<Rectangle>();

            if(methodLog) Jupiter.Log.EndMethod();
        }

		/// <summary>
		/// Lee el color del pixel, forzando A=0
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		Color GetPixelWithoutAlpha(int x, int y)
		{
			Color color=data[x + y * Texture.Width];
			color.A = 0;
			return color;
		}

		/// <summary>
		/// Lee el color del pixel
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		Color GetPixel(int x, int y)
		{
            Color color = data[x + y * Texture.Width];
			return color;
		}

        public void CreateFixedSizeClips(bool keepDataOnSystemMemory)
        {
            CreateFixedSizeClips(keepDataOnSystemMemory, Texture.Width, Texture.Height);
        }

        public void CreateFixedSizeClips(bool keepDataOnSystemMemory,int spriteWidth, int spriteHeight)
        {
            if (keepDataOnSystemMemory)
            {
                int size = Texture.Width * Texture.Height;

                data = new Color[size];

                Texture.GetData<Color>(data);
            }

            for (int y = 0; y < Texture.Height; y += spriteHeight)
            {
                for (int x = 0; x < Texture.Width; x += spriteWidth)
                {
                    Rectangle rect = new Rectangle(x, y, spriteWidth, spriteHeight);

                    SpriteRectangles.Add(rect);
                }
            }

            CreateClips();
        }

        void CreateClips()
        {
            Clips = new List<Clip>();

            int i=0;

            foreach (Rectangle sprite in SpriteRectangles)
            {
                Clip clip=new Clip(this,i++);
                Clips.Add(clip);
            }
        }

		/// <summary>
		/// Crea los clips. La fila 0 no puede contener información de clips. Y el pixel 0,0 es el que define el color a comprobar
		/// </summary>
		/// <param name="keepDataOnSystemMemory">Indica si debemos guardar la textura también en memoria de sistema. Se utiliza para las colisiones.</param>
		/// <param name="mode">Método usado para calcular los clips</param>
		public void CreateClips(bool keepDataOnSystemMemory, TextureClipModes mode,bool makeBackgroundTransparent,bool makeRectangleTransparent)
		{
			int size=Texture.Width * Texture.Height;

			data = new Color[size];

			Texture.GetData<Color>(data);
			
			// Cojo el color del fondo
			Color fondo = GetPixelWithoutAlpha(0, 0);

			for (int y = 1; y < Texture.Height; y++)
			{
				for (int x = 0; x < Texture.Width; x++)
				{

					Color pixel = GetPixelWithoutAlpha(x, y);

					if (mode == TextureClipModes.Pixel0IsBackground)
					{
						if (pixel == fondo) continue;
					}
					else
					{
						if (pixel != fondo) continue;
					}

					// Compruebo si estoy dentro de un clip ya

					bool next = false;

					foreach (Rectangle r in SpriteRectangles)
					{
						r.Inflate(1, 1);

						if (r.Contains(x, y))
						{
							x = r.Right + 1;

							next = true;

							break;
						}
					}

					if (next) continue;

					int width=0, height=0;

					// Encuentro un pixel diferente, así que debe ser el recuadro
					// Busco el límite derecho
					for(int tx=x;tx<Texture.Width;tx++)
					{
						if (GetPixelWithoutAlpha(tx, y) != pixel) break;

						width++;
					}
					// Ahora el límite inferior
					for (int ty = y; ty < Texture.Height; ty++)
					{
						if (GetPixelWithoutAlpha(x, ty) != pixel) break;

						height++;
					}

					Rectangle rect = new Rectangle(x + 1, y + 1, width-2, height-2);

					SpriteRectangles.Add(rect);

					// me posiciono a la derecha de este clip
					x += width ;

				}
			}

			// Ahora dibujo transparente lo que es de color de fondo

			if (makeBackgroundTransparent)
			{

				for (int i = 0; i < size; i++)
				{
					Color color = data[i];
					color.A = 0;
					if (color == fondo)
						data[i].A = 0;
				}
			}

			if (makeRectangleTransparent)
			{

				// Y dibujo también transparente el rectángulo. Esto debo hacerlo porque si utilizo filtrado,
				// me coge el color del rectángulo también

				int il = SpriteRectangles.Count;

				for (int ic = 0; ic < il; ic++)
				{
					Rectangle r = SpriteRectangles[ic];

					for (int x = r.Left - 1; x <= r.Right ; x++)
					{
						for (int y = r.Top - 1; y <= r.Bottom; y++)
						{
							if (x == r.Left - 1 || x == r.Right  || y == r.Top - 1 || y == r.Bottom)
								data[x + y * Texture.Width].A = 0;
						}
					}

				}
			}

			if(makeRectangleTransparent || makeBackgroundTransparent) Texture.SetData<Color>(data);

			if(!keepDataOnSystemMemory) data = null;

            CreateClips();
		}

		/// <summary>
		/// Busca en un clip el punto donde está un color
		/// </summary>
		/// <param name="clipNumber"></param>
		/// <returns>Si el valor devuelto tiene X=-1, no lo ha encontrado</returns>
		public Point CheckColor(int clipNumber,Color colorToCheck)
		{
			foreach (Rectangle r in SpriteRectangles)
			{
				for (int x = r.Left; x < r.Right; x++)
				{
					for (int y = r.Top ; y < r.Bottom; y++)
					{
						if (GetPixelWithoutAlpha(x, y) == colorToCheck) return new Point(x-r.Left, y-r.Top);
					}
				}
			}
			return new Point(-1, -1);
		}

		/// <summary>
		/// Comprueba la colisión al pixel entre este clip y otro, devolviendo el primer punto de colision si existe
		/// Los ptos de colisión tienen que estar referidos a cada clip
		/// El pto de colision que devuelve están en el space del rectángulo de intersección entre el Clip A y el B
		/// </summary>
		/// <param name="clipNumber"></param>
		/// <param name="other"></param>
		/// <param name="clipNumberB"></param>
		/// <param name="collisionRectangle"></param>
		/// <param name="firstPixelCollision"></param>
		/// <returns></returns>
		public bool PixelCollide(int clipNumber, Texture_2D other, int clipNumberB, Point collisionPtoA, Point CollisionPtoB, int width, int height, bool invertX, bool invertY, out Point firstPixelCollision)
		{
			firstPixelCollision = Point.Zero;

			Rectangle rectA=SpriteRectangles[clipNumber];
			Rectangle rectB = other.SpriteRectangles[clipNumberB];

			for (int y = 0; y < height; y++)
			{
				for (int x = 0; x < width; x++)
				{

					int xf = x;
					int yf = y;

					// Queremos que el escaneo se haga de derecha a izquierda
					if (invertX) xf = (width - 1) - x;

					// Queremos que el escaneo se haga de izquierda a derecha
					if (invertY) yf = (height - 1) - y;

					// Coloco este pto en el pixel del rectángulo A
					int xA = xf + collisionPtoA.X + rectA.Left;
					int yA = yf + collisionPtoA.Y + rectA.Top;

					// Coloco este pto en el pixel del rectángulo B
					int xB = xf + CollisionPtoB.X + rectB.Left;
					int yB = yf + CollisionPtoB.Y + rectB.Top;

					// Compruebo si este pto tiene colisión en el Alpha

					Color colorA = GetPixel(xA, yA);
					Color colorB = other.GetPixel(xB, yB);

					if (colorA.A != 0 && colorB.A != 0)
					{
						firstPixelCollision.X = xf;
						firstPixelCollision.Y = yf;
						return true;
					}
				}
			}

			return false;
		}

		/// <summary>
		/// Comprueba la colisión al pixel en un clip determinado, devolviendo el primer punto de colision si existe
		/// Los ptos de colisión tienen que estar referidos a cada clip
		/// El pto de colision que devuelve están en el space del rectángulo de intersección entre el Clip A y el B
		/// </summary>
		/// <param name="clipNumber"></param>
		/// <param name="other"></param>
		/// <param name="clipNumberB"></param>
		/// <param name="collisionRectangle"></param>
		/// <param name="firstPixelCollision"></param>
		/// <returns></returns>
		public bool PixelCollide(int clipNumber, Point collisionPtoA, int width, int height, bool invertX, bool invertY, out Point firstPixelCollision)
		{
			firstPixelCollision = Point.Zero;

			Rectangle rectA = SpriteRectangles[clipNumber];

			for (int y = 0; y < height; y++)
			{
				for (int x = 0; x < width; x++)
				{

					int xf = x;
					int yf = y;

					// Queremos que el escaneo se haga de derecha a izquierda
					if (invertX) xf = (width - 1) - x;

					// Queremos que el escaneo se haga de izquierda a derecha
					if (invertY) yf = (height - 1) - y;

					// Coloco este pto en el pixel del rectángulo A
					int xA = xf + collisionPtoA.X + rectA.Left;
					int yA = yf + collisionPtoA.Y + rectA.Top;

					// Compruebo si este pto tiene colisión en el Alpha

					Color colorA = GetPixel(xA, yA);

					if (colorA.A != 0)
					{
						firstPixelCollision.X = xf;
						firstPixelCollision.Y = yf;
						return true;
					}
				}
			}

			return false;
		}

		/// <summary>
		/// Devuelve el clip indicado
		/// </summary>
		/// <param name="index">Nº del clip, empezando por 0</param>
		/// <returns></returns>
		public Rectangle GetClip(int index)
		{
			return SpriteRectangles[index];
		}

		/// <summary>
		/// Convierte el color de fondo en transparente basándose como color de fondo en el pixel 0,0
		/// </summary>
		/// <param name="texture"></param>
		public void ConvertBackColorToAlpha(Texture2D texture)
		{
			int size = texture.Width * texture.Height;

			data = new Color[size];

			texture.GetData<Color>(data);

			// Cojo el color del fondo
			Color fondo = GetPixelWithoutAlpha(0, 0);

			// fuerzo el alpha a 0 para hacer las comprobaciones

			fondo.A = 0;

			// Ahora dibujo transparente lo que es de color de fondo

			for (int i = 0; i < size; i++)
			{
				Color color = data[i];
				color.A = 0;
				if (color == fondo)
					data[i].A = 0;
			}

			texture.SetData<Color>(data);

			data = null;
		}
    }

    public class Textures
    {
        public List<Texture_2D> List { get; private set; }

        public Textures()
        {
            List = new List<Texture_2D>();
        }

        public Texture_2D CreateAndAdd(string fullpath, bool useContent)
        {
            Texture_2D texture = new Texture_2D(fullpath, useContent);

            List.Add(texture);

            return texture;
        }
    }
}
